// Copyright (C) 1999-2000 Id Software, Inc.
//

// every line of code that differs from the quake3:arena SDK
// is property of manfred nerurkar
// no commercial explotation allowed
// you are only allowed to use this code in navy seals: covert operations
// a quake3 arena modifiation
// defcon-x@ns-co.net

#include "g_local.h"


level_locals_t	level;

typedef struct {
    vmCvar_t	*vmCvar;
    char		*cvarName;
    char		*defaultString;
    int			cvarFlags;
    int			modificationCount;  // for tracking changes
    qboolean	trackChange;			    // track this variable, and announce if changed
    qboolean teamShader;			    // track and if changed, update shader state
} cvarTable_t;

gentity_t		g_entities[MAX_GENTITIES];
gclient_t		g_clients[MAX_CLIENTS];

vmCvar_t	g_gametype;
vmCvar_t	g_dmflags;
vmCvar_t	g_fraglimit;
vmCvar_t	g_timelimit;
vmCvar_t	g_capturelimit;
vmCvar_t	g_friendlyFire;
vmCvar_t	g_password;
vmCvar_t	g_needpass;
vmCvar_t	g_maxclients;
vmCvar_t	g_maxGameClients;
vmCvar_t	g_dedicated;
vmCvar_t	g_speed;
vmCvar_t	g_gravity;
vmCvar_t	g_cheats;
vmCvar_t	g_knockback;
vmCvar_t	g_forcerespawn;
vmCvar_t	g_inactivity;
vmCvar_t	g_debugMove;
vmCvar_t	g_debugDamage;
vmCvar_t	g_debugAlloc;
vmCvar_t	g_weaponRespawn;
vmCvar_t	g_weaponTeamRespawn;
vmCvar_t	g_motd;
vmCvar_t	g_synchronousClients;
vmCvar_t	g_warmup;
vmCvar_t	g_doWarmup;
vmCvar_t	g_restarted;
vmCvar_t	g_log;
vmCvar_t	g_logSync;
vmCvar_t	g_blood;
vmCvar_t	g_podiumDist;
vmCvar_t	g_podiumDrop;
vmCvar_t	g_allowVote;
vmCvar_t	g_teamAutoJoin;
vmCvar_t	g_teamForceBalance;
vmCvar_t	g_banIPs;
vmCvar_t	g_filterBan;
vmCvar_t	g_smoothClients;
vmCvar_t	pmove_fixed;
vmCvar_t	pmove_msec;
vmCvar_t	g_rankings;
vmCvar_t	g_listEntity;
#ifdef MISSIONPACK
vmCvar_t	g_obeliskHealth;
vmCvar_t	g_obeliskRegenPeriod;
vmCvar_t	g_obeliskRegenAmount;
vmCvar_t	g_obeliskRespawnDelay;
vmCvar_t	g_cubeTimeout;
vmCvar_t	g_redteam;
vmCvar_t	g_blueteam;
vmCvar_t	g_singlePlayer;
vmCvar_t	g_enableDust;
vmCvar_t	g_enableBreath;
vmCvar_t	g_proxMineTimeout;
#endif
// Navy Seals ++
vmCvar_t	g_logradio;
vmCvar_t	g_allowKnifes;
vmCvar_t	g_maxTeamKill;
vmCvar_t	g_roundTime;
vmCvar_t	g_keepCharacter;
vmCvar_t	g_stopbots;
vmCvar_t	g_overrideGoals;
vmCvar_t	g_invenTime;
vmCvar_t	g_bombTime;

vmCvar_t	g_baseXp;

vmCvar_t	g_leedvelocity;
vmCvar_t	g_shotgunleedvelocity;

vmCvar_t	g_riflemod;
vmCvar_t	g_pistolmod;
vmCvar_t	g_snipermod;
vmCvar_t	g_bulletDamage;

vmCvar_t	g_silentBullets;
vmCvar_t	g_realLead;

vmCvar_t	g_teamlockcamera;
vmCvar_t	g_testSmoke;

vmCvar_t	g_minPlayers;

vmCvar_t	g_allowMapVote;
vmCvar_t	g_allowKickVote;
vmCvar_t	g_allowTimelimitVote;
vmCvar_t	g_allowTeampointlimitVote;

vmCvar_t	g_noGrenades;
vmCvar_t	g_noPrimary;
vmCvar_t	g_noSecondary;

vmCvar_t	g_test;
vmCvar_t  g_hbbox_min0;
vmCvar_t  g_hbbox_min1;
vmCvar_t  g_hbbox_min2;
vmCvar_t  g_hbbox_max0;
vmCvar_t  g_hbbox_max1;
vmCvar_t  g_hbbox_max2;

vmCvar_t	g_updateServerInfoTime;
vmCvar_t	g_TeamPlayers;
vmCvar_t	g_TeamScores;

vmCvar_t	g_TeamKillRemoveTime;
vmCvar_t	g_firstCountdown;
vmCvar_t	g_matchLockXP;
vmCvar_t	g_LockXP;
vmCvar_t	g_teamXp;
vmCvar_t	g_teamRespawn;
vmCvar_t	g_squadAssault;
vmCvar_t  g_antilag;

qboolean	b_sWaitingForPlayers = qfalse;
int			i_sNextWaitPrint = 0;
int			GameState = 0;
int			LTS_Rounds = 1;
int			i_sCountDown;
// Navy Seals --

cvarTable_t		gameCvarTable[] = {
                                   // don't override the cheat state set by the system
                                   { &g_cheats, "sv_cheats", "", 0, 0, qfalse },

                                   // noset vars
                                   { NULL, "gameversion", GAME_VERSION , CVAR_SERVERINFO | CVAR_ROM, 0, qfalse  },
                                   { NULL, "gamename", GAMEVERSION , CVAR_SERVERINFO | CVAR_ROM, 0, qfalse  },
                                   { NULL, "gamedate", __DATE__ , CVAR_ROM, 0, qfalse  },
                                   { &g_restarted, "g_restarted", "0", CVAR_ROM, 0, qfalse  },
                                   { NULL, "sv_mapname", "", CVAR_SERVERINFO | CVAR_ROM, 0, qfalse  },

                                   // latched vars
                                   { &g_gametype, "g_gametype", "2", CVAR_SERVERINFO  | CVAR_LATCH , 0, qfalse  },

                                   { &g_maxclients, "sv_maxclients", "8", CVAR_SERVERINFO | CVAR_LATCH | CVAR_ARCHIVE, 0, qfalse  },
                                   { &g_maxGameClients, "g_maxGameClients", "0", CVAR_SERVERINFO | CVAR_LATCH | CVAR_ARCHIVE, 0, qfalse  },

                                   // change anytime vars
                                   { &g_dmflags, "dmflags", "0", CVAR_SERVERINFO | CVAR_ARCHIVE, 0, qtrue  },
                                   { &g_fraglimit, "fraglimit", "20", CVAR_ARCHIVE | CVAR_NORESTART, 0, qtrue },
                                   { &g_timelimit, "timelimit", "0", CVAR_SERVERINFO | CVAR_ARCHIVE | CVAR_NORESTART, 0, qtrue },
                                   { &g_capturelimit, "teampointlimit", "8", CVAR_SERVERINFO | CVAR_ARCHIVE | CVAR_NORESTART, 0, qtrue },

                                   { &g_synchronousClients, "g_synchronousClients", "0", CVAR_SYSTEMINFO, 0, qfalse  },

                                   { &g_friendlyFire, "g_friendlyFire", "0", CVAR_SERVERINFO | CVAR_ARCHIVE, 0, qtrue  },

                                   { &g_teamAutoJoin, "g_teamAutoJoin", "0", CVAR_ARCHIVE  },
                                   { &g_teamForceBalance, "g_teamForceBalance", "0", CVAR_ARCHIVE  },

                                   { &g_warmup, "g_warmup", "20", CVAR_ARCHIVE, 0, qtrue  },
                                   { &g_doWarmup, "g_doWarmup", "0", 0, 0, qtrue  },
                                   { &g_log, "g_log", "server.log", CVAR_ARCHIVE, 0, qfalse  },
                                   { &g_logSync, "g_logSync", "0", CVAR_ARCHIVE, 0, qfalse  },

                                   { &g_password, "g_password", "", CVAR_USERINFO, 0, qfalse  },

                                   { &g_banIPs, "g_banIPs", "", CVAR_ARCHIVE, 0, qfalse  },
                                   { &g_filterBan, "g_filterBan", "1", CVAR_ARCHIVE, 0, qfalse  },

                                   { &g_needpass, "g_needpass", "0", CVAR_SERVERINFO | CVAR_ROM, 0, qfalse },

                                   { &g_dedicated, "dedicated", "0", 0, 0, qfalse  },

                                   { &g_speed, "g_speed", "180", 0, 0, qtrue  },
                                   { &g_gravity, "g_gravity", "800", 0, 0, qtrue  },
                                   { &g_knockback, "g_knockback", "200", 0, 0, qtrue  }, // 1000
                                   { &g_weaponRespawn, "g_weaponrespawn", "5", 0, 0, qtrue  },
                                   { &g_weaponTeamRespawn, "g_weaponTeamRespawn", "30", 0, 0, qtrue },
                                   { &g_forcerespawn, "g_forcerespawn", "20", 0, 0, qtrue },
                                   // inactivity time kick [ kick after X seconds ]
                                   { &g_inactivity, "g_inactivity", "0", 0, 0, qtrue },

                                   { &g_debugMove, "g_debugMove", "0", 0, 0, qfalse },
                                   { &g_debugDamage, "g_debugDamage", "0", 0, 0, qfalse },
                                   { &g_debugAlloc, "g_debugAlloc", "0", 0, 0, qfalse },
                                   { &g_motd, "g_motd", "", 0, 0, qfalse },
                                   { &g_blood, "com_blood", "1", 0, 0, qfalse },

                                   { &g_podiumDist, "g_podiumDist", "80", 0, 0, qfalse },
                                   { &g_podiumDrop, "g_podiumDrop", "70", 0, 0, qfalse },

                                   { &g_allowVote, "g_allowVote", "1", CVAR_ARCHIVE, 0, qfalse },
                                   { &g_listEntity, "g_listEntity", "0", 0, 0, qfalse },

                                   { &g_smoothClients, "g_smoothClients", "1", 0, 0, qfalse},
                                   { &pmove_fixed, "pmove_fixed", "0", CVAR_SYSTEMINFO, 0, qfalse},
                                   { &pmove_msec, "pmove_msec", "8", CVAR_SYSTEMINFO, 0, qfalse},
                                   // Navy Seals ++
                                   { &g_logradio, "g_logradio", "1", 0, 0, qfalse },
                                   { &g_keepCharacter, "g_keepCharacter", "1", 0, 0, qfalse },

                                   // server infos [ vmcvars that need to be visible to the client]
                                   { &g_allowKnifes, "g_allowKnifes", "1", 0, 0, qtrue },
                                   { &g_maxTeamKill, "g_maxTeamKill", "3", CVAR_SERVERINFO, 0, qtrue },
                                   { &g_roundTime, "roundtime", "5", CVAR_SERVERINFO, 0, qtrue },

                                   { &g_overrideGoals, "g_overridegoals", "0", CVAR_SERVERINFO | CVAR_LATCH, 0, qtrue },

                                   { &g_aimCorrect, "g_aimCorrect", "22", CVAR_LATCH, 0, qfalse},
                                   // time X in seconds the player can change their inventory from round begin
                                   { &g_invenTime, "g_inventoryUpdateTime", "5", 0, 0, qfalse},
                                   { &g_bombTime, "g_bombTime", "80", 0, 0, qfalse },
                                   // Navy Seals --
                                   { &g_stopbots, "g_stopbots", "0", 0, 0, qfalse},
                                   { &g_rankings, "g_rankings", "0", 0, 0, qfalse},
                                   { &g_baseXp, "g_baseXp", "15", 0,0, qfalse },

                                   { &g_pistolmod, "g_pistolmod", "1.0", 0, 0, qfalse},
                                   { &g_riflemod, "g_riflemod", "1.35", 0, 0, qfalse},
                                   { &g_snipermod, "g_snipermod", "1.7", 0,0, qfalse },

                                   { &g_leedvelocity, "g_leedvelocity", "10000", 0, 0, qfalse},
                                   { &g_shotgunleedvelocity, "g_shotgunleedvelocity", "15000", 0,0, qfalse },

                                   { &g_silentBullets, "g_silentBullets", "0", 0,0, qfalse },
                                   { &g_realLead, "g_realLead", "0", 0,0, qfalse },

                                   { &g_teamlockcamera, "g_teamlockcamera", "0", 0,0, qfalse },

                                   { &g_testSmoke, "g_testSmoke", "0", 0,0, qfalse },

                                   { &g_bulletDamage, "g_bulletDamage", "0", 0, 0, qfalse },

                                   { &g_noPrimary, "g_noPrimary", "0", 0, 0, qfalse },
                                   { &g_noSecondary, "g_noSecondary", "0", 0, 0, qfalse },
                                   { &g_noGrenades, "g_noGrenades", "0", 0, 0, qfalse },

                                   { &g_minPlayers, "g_minPlayers", "1", 0, 0, qfalse },

                                   { &g_allowMapVote, "g_allowMapVote", "1", 0, 0, qfalse },
                                   { &g_allowKickVote, "g_allowKickVote", "1", 0, 0, qfalse },
                                   { &g_allowTimelimitVote, "g_allowTimelimitVote", "1", 0, 0, qfalse },
                                   { &g_allowTeampointlimitVote, "g_allowTeampointlimitVote", "1", 0, 0, qfalse },

                                   // mr CGB variables
                                   { &g_updateServerInfoTime, "g_updateServerInfoTime", "5000",0,0,qfalse },
                                   { &g_TeamPlayers, "g_TeamPlayers", "(None)", CVAR_SERVERINFO , 0, qfalse  },
                                   { &g_TeamScores, "g_TeamScores", "(None)", CVAR_SERVERINFO , 0, qfalse  },

                                   // GT_TEAM
                                   { &g_teamRespawn, "g_teamRespawn", "10", CVAR_SERVERINFO | CVAR_ARCHIVE | CVAR_NORESTART,0, qfalse },
                                   { &g_squadAssault, "g_squadAssault", "0", CVAR_LATCH | CVAR_SERVERINFO, 0, qfalse },
                                   { &g_antilag, "g_antilag", "1", 0, 0, qfalse },
                                   { &g_teamXp, "g_teamXp", "60", CVAR_LATCH,0, qfalse },

                                   { &g_TeamKillRemoveTime, "g_TeamKillRemoveTime", "300", 0, 0, qfalse },
                                   { &g_firstCountdown, "g_firstCountdown", "30", 0,0, qfalse },

                                   // my testing cvar
                                   { &g_test, "g_test", "0", 0,0, qfalse },
                                   { &g_hbbox_min0, "g_hbbox_min0", "-3", 0,0, qfalse },
                                   { &g_hbbox_min1, "g_hbbox_min1", "-2", 0,0, qfalse },
                                   { &g_hbbox_min2, "g_hbbox_min2", "-1", 0,0, qfalse },
                                   { &g_hbbox_max0, "g_hbbox_max0", "3.5", 0,0, qfalse },
                                   { &g_hbbox_max1, "g_hbbox_max1", "2.5", 0,0, qfalse },
                                   { &g_hbbox_max2, "g_hbbox_max2", "8", 0,0, qfalse },
                                   { &g_mapcycle, "g_mapcycle", "configs/mapcycle.cfg", 0, 0, qfalse },

                                   { &g_matchLockXP, "g_matchLockXP", "0", CVAR_LATCH | CVAR_SERVERINFO,0, qfalse },
                                   { &g_LockXP, "g_LockXP", "0", CVAR_LATCH | CVAR_SERVERINFO,0, qfalse },

                               };

int		gameCvarTableSize = sizeof( gameCvarTable ) / sizeof( gameCvarTable[0] );


void G_InitGame( int levelTime, int randomSeed, int restart );
void G_RunFrame( int levelTime );
void G_ShutdownGame( int restart );
void CheckExitRules( void );


/*
================
vmMain

This is the only way control passes into the module.
This must be the very first function compiled into the .q3vm file
================
*/
int vmMain( int command, int arg0, int arg1, int arg2, int arg3, int arg4, int arg5, int arg6, int arg7, int arg8, int arg9, int arg10, int arg11  ) {
    switch ( command ) {
    case GAME_INIT:
        G_InitGame( arg0, arg1, arg2 );
        return 0;
    case GAME_SHUTDOWN:
        G_ShutdownGame( arg0 );
        return 0;
    case GAME_CLIENT_CONNECT:
        return (int)ClientConnect( arg0, arg1, arg2 );
    case GAME_CLIENT_THINK:
        ClientThink( arg0 );
        return 0;
    case GAME_CLIENT_USERINFO_CHANGED:
        ClientUserinfoChanged( arg0 );
        return 0;
    case GAME_CLIENT_DISCONNECT:
        ClientDisconnect( arg0 );
        return 0;
    case GAME_CLIENT_BEGIN:
        ClientBegin( arg0 );
        return 0;
    case GAME_CLIENT_COMMAND:
        ClientCommand( arg0 );
        return 0;
    case GAME_RUN_FRAME:
        G_RunFrame( arg0 );
        return 0;
    case GAME_CONSOLE_COMMAND:
        return ConsoleCommand();
    case BOTAI_START_FRAME:
        return 0;
    }

    return -1;
}


void QDECL G_Printf( const char *fmt, ... ) {
    va_list		argptr;
    char		text[1024];

    va_start (argptr, fmt);
    vsprintf (text, fmt, argptr);
    va_end (argptr);

    trap_Printf( text );
}

void QDECL G_Error( const char *fmt, ... ) {
    va_list		argptr;
    char		text[1024];

    va_start (argptr, fmt);
    vsprintf (text, fmt, argptr);
    va_end (argptr);

    trap_Error( text );
}

/*
================
G_FindTeams

Chain together all entities with a matching team field.
Entity teams are used for item groups and multi-entity mover groups.

All but the first will have the FL_TEAMSLAVE flag set and teammaster field set
All but the last will have the teamchain field set to the next one
================
*/
void G_FindTeams( void ) {
    gentity_t	*e, *e2;
    int		i, j;
    int		c, c2;

    c = 0;
    c2 = 0;
    for ( i=1, e=g_entities+i ; i < level.num_entities ; i++,e++ ){
        if (!e->inuse)
            continue;
        if (!e->team)
            continue;
        if (e->flags & FL_TEAMSLAVE)
            continue;
        e->teammaster = e;
        c++;
        c2++;
        for (j=i+1, e2=e+1 ; j < level.num_entities ; j++,e2++)
        {
            if (!e2->inuse)
                continue;
            if (!e2->team)
                continue;
            if (e2->flags & FL_TEAMSLAVE)
                continue;
            if (!strcmp(e->team, e2->team))
            {
                c2++;
                e2->teamchain = e->teamchain;
                e->teamchain = e2;
                e2->teammaster = e;
                e2->flags |= FL_TEAMSLAVE;

                // make sure that targets only point at the master
                if ( e2->targetname ) {
                    e->targetname = e2->targetname;
                    e2->targetname = NULL;
                }
            }
        }
    }

    G_Printf ("%i teams with %i entities\n", c, c2);
}

void G_RemapTeamShaders() {
#ifdef MISSIONPACK
    char string[1024];
    float f = level.time * 0.001;
    Com_sprintf( string, sizeof(string), "team_icon/%s_red", g_redteam.string );
    AddRemap("textures/ctf2/redteam01", string, f);
    AddRemap("textures/ctf2/redteam02", string, f);
    Com_sprintf( string, sizeof(string), "team_icon/%s_blue", g_blueteam.string );
    AddRemap("textures/ctf2/blueteam01", string, f);
    AddRemap("textures/ctf2/blueteam02", string, f);
    trap_SetConfigstring(CS_SHADERSTATE, BuildShaderStateConfig());
#endif
}


/*
=================
G_RegisterCvars
=================
*/
void G_RegisterCvars( void ) {
    int			i;
    cvarTable_t	*cv;
    qboolean remapped = qfalse;

    for ( i = 0, cv = gameCvarTable ; i < gameCvarTableSize ; i++, cv++ ) {
        trap_Cvar_Register( cv->vmCvar, cv->cvarName,
                            cv->defaultString, cv->cvarFlags );
        if ( cv->vmCvar )
            cv->modificationCount = cv->vmCvar->modificationCount;

        if (cv->teamShader) {
            remapped = qtrue;
        }
    }

    if (remapped) {
        G_RemapTeamShaders();
    }

    // check some things
    if ( g_gametype.integer <= GT_FFA || g_gametype.integer == 2 || g_gametype.integer >= GT_MAX_GAME_TYPE ) {
        G_Printf( "g_gametype %i is out of range, defaulting to %i\n", g_gametype.integer, GT_LTS );
        g_gametype.value = g_gametype.integer = GT_LTS;
        trap_Cvar_Set( "g_gametype", va("%i", GT_LTS)  );
        trap_SendConsoleCommand( EXEC_INSERT, "map_restart\n");
    }

    level.warmupModificationCount = g_warmup.modificationCount;
}

/*
=================
G_UpdateCvars
=================
*/
void G_UpdateCvars( void ) {
    int			i;
    cvarTable_t	*cv;
    qboolean remapped = qfalse;

    for ( i = 0, cv = gameCvarTable ; i < gameCvarTableSize ; i++, cv++ ) {
        if ( cv->vmCvar ) {
            trap_Cvar_Update( cv->vmCvar );

            if ( cv->modificationCount != cv->vmCvar->modificationCount ) {
                cv->modificationCount = cv->vmCvar->modificationCount;

                if ( cv->trackChange ) {
                    trap_SendServerCommand( -1, va("print \"Server: %s changed to %s\n\"",
                                                   cv->cvarName, cv->vmCvar->string ) );
                }

                if (cv->teamShader) {
                    remapped = qtrue;
                }
            }
        }
    }

    if (remapped) {
        G_RemapTeamShaders();
    }
}

void	G_ParseRadioConfigFileForTeam( int team );
/*
============
G_InitGame

============
*/
void G_InitGame( int levelTime, int randomSeed, int restart ) {
    int					i;

    // lan protection.
    {
        //		char var[128];

        /*	trap_Cvar_VariableStringBuffer("lan_protect", var, sizeof(var) );

        if (!Q_stricmp( var, "exitus") )
        ;
        else
        G_Error("Wrong LAN Password.\nServer won't start without password.\n");
        */	}
    G_Printf ("------- Game Initialization -------\n");
    G_Printf ("gamename: %s\n", GAMEVERSION);
    G_Printf ("gamedate: %s\n", __DATE__);

    srand( randomSeed );

    G_RegisterCvars();

    G_ProcessIPBans();

    G_InitMemory();

    // set some level globals
    memset( &level, 0, sizeof( level ) );
    level.time = levelTime;
    level.startTime = levelTime;

    // Navy Seals ++
    level.num_objectives[TEAM_RED] = level.num_objectives[TEAM_BLUE] = 0;
    level.done_objectives[TEAM_RED] = level.num_objectives[TEAM_BLUE] = 0;
    level.bombs[TEAM_RED] = level.bombs[TEAM_BLUE] = 0;
    lastvip[TEAM_RED] = lastvip[TEAM_BLUE] = -1;

    // assault mode
    level.fields[TEAM_RED] = level.fields[TEAM_BLUE] = 0;

    level.roundstartTime = level.vipTime = 0;

    // load radio files
    G_ParseRadioConfigFileForTeam( TEAM_RED );
    G_ParseRadioConfigFileForTeam( TEAM_BLUE );

    // Navy Seals --
    level.snd_fry = G_SoundIndex("sound/player/fry.wav");	// FIXME standing in lava / slime

    level.breathsnd_male = G_SoundIndex("sound/actors/br_run_m.wav");	// FIXME standing in lava / slime
    level.breathsnd_female = G_SoundIndex("sound/actors/br_run_f.wav"); // FIXME standing in lava / slime
    level.breathsnd_injured = G_SoundIndex("sound/actors/br_injured_m.wav"); // FIXME standing in lava / slime


    if ( g_log.string[0] ) {
        if ( g_logSync.integer ) {
            trap_FS_FOpenFile( g_log.string, &level.logFile, FS_APPEND_SYNC );
        } else {
            trap_FS_FOpenFile( g_log.string, &level.logFile, FS_APPEND );
        }
        if ( !level.logFile ) {
            G_Printf( "WARNING: Couldn't open logfile: %s\n", g_log.string );
        } else {
            char	serverinfo[MAX_INFO_STRING];

            trap_GetServerinfo( serverinfo, sizeof( serverinfo ) );

            G_LogPrintf("------------------------------------------------------------\n" );
            G_LogPrintf("InitGame: %s\n", serverinfo );
        }
    } else {
        G_Printf( "Not logging to disk.\n" );
    }

    G_InitWorldSession();

    // initialize all entities for this game
    memset( g_entities, 0, MAX_GENTITIES * sizeof(g_entities[0]) );
    level.gentities = g_entities;

    // initialize all clients for this game
    level.maxclients = g_maxclients.integer;
    memset( g_clients, 0, MAX_CLIENTS * sizeof(g_clients[0]) );
    level.clients = g_clients;

    // set client fields on player ents
    for ( i=0 ; i<level.maxclients ; i++ ) {
        g_entities[i].client = level.clients + i;
    }

    // always leave room for the max number of clients,
    // even if they aren't all used, so numbers inside that
    // range are NEVER anything but clients
    level.num_entities = MAX_CLIENTS;

    // let the server system know where the entites are
    trap_LocateGameData( level.gentities, level.num_entities, sizeof( gentity_t ),
                         &level.clients[0].ps, sizeof( level.clients[0] ) );

    // reserve some spots for dead player bodies
    InitBodyQue();

    ClearRegisteredItems();

    // parse the key/value pairs and spawn gentities
    G_SpawnEntitiesFromString();

    // general initialization
    G_FindTeams();

    // make sure we have flags for CTF, etc
    if( g_gametype.integer >= GT_TEAM ) {
        G_CheckTeamItems();
    }

    SaveRegisteredItems();

    G_Printf ("-----------------------------------\n");
    // Navy Seals ++
    if( g_gametype.integer >= GT_TEAM )
        NS_RemoveItems();
    // Navy Seals --
    /*
    if( g_gametype.integer == GT_TRAINING || trap_Cvar_VariableIntegerValue( "com_buildScript" ) ) {
    G_ModelIndex( SP_PODIUM_MODEL );
    G_SoundIndex( "sound/player/gurp1.wav" );
    G_SoundIndex( "sound/player/gurp2.wav" );
    }
    */
#ifdef MISSIONPACK
    G_RemapTeamShaders();
#endif
    NS_InitMapCycle();
    NS_InitHeadBBoxes();

    NS_InitHeadGear( ) ;
}



/*
=================
G_ShutdownGame
=================
*/
void G_ShutdownGame( int restart ) {
    G_Printf ("==== ShutdownGame ====\n");

    if ( level.logFile ) {
        G_LogPrintf("ShutdownGame:\n" );
        G_LogPrintf("------------------------------------------------------------\n" );
        trap_FS_FCloseFile( level.logFile );
    }

    // write all the client session data so we can get it back
    G_WriteSessionData();
}



//===================================================================

#ifndef GAME_HARD_LINKED
// this is only here so the functions in q_shared.c and bg_*.c can link

void QDECL Com_Error ( int level, const char *error, ... ) {
    va_list		argptr;
    char		text[1024];

    va_start (argptr, error);
    vsprintf (text, error, argptr);
    va_end (argptr);

    G_Error( "%s", text);
}

void QDECL Com_Printf( const char *msg, ... ) {
    va_list		argptr;
    char		text[1024];

    va_start (argptr, msg);
    vsprintf (text, msg, argptr);
    va_end (argptr);

    G_Printf ("%s", text);
}

#endif

/*
========================================================================

PLAYER COUNTING / SCORE SORTING

========================================================================
*/

/*
=============
AddTournamentPlayer

If there are less than two tournament players, put a
spectator in the game and restart
=============
*/
void AddTournamentPlayer( void ) {
    int			i;
    gclient_t	*client;
    gclient_t	*nextInLine;

    if ( level.numPlayingClients >= 2 ) {
        return;
    }

    // never change during intermission
    if ( level.intermissiontime ) {
        return;
    }

    nextInLine = NULL;

    for ( i = 0 ; i < level.maxclients ; i++ ) {
        client = &level.clients[i];
        if ( client->pers.connected != CON_CONNECTED ) {
            continue;
        }
        if ( client->sess.sessionTeam != TEAM_SPECTATOR ) {
            continue;
        }
        // never select the dedicated follow or scoreboard clients
        if ( client->sess.spectatorState == SPECTATOR_SCOREBOARD ||
                client->sess.spectatorClient < 0  ) {
            continue;
        }

        if ( !nextInLine || client->sess.spectatorTime < nextInLine->sess.spectatorTime ) {
            nextInLine = client;
        }
    }

    if ( !nextInLine ) {
        return;
    }

    level.warmupTime = -1;

    // set them to free-for-all team
    SetTeam( &g_entities[ nextInLine - level.clients ], "f" );
}

/*
=======================
RemoveTournamentLoser

Make the loser a spectator at the back of the line
=======================
*/
void RemoveTournamentLoser( void ) {
    int			clientNum;

    if ( level.numPlayingClients != 2 ) {
        return;
    }

    clientNum = level.sortedClients[1];

    if ( level.clients[ clientNum ].pers.connected != CON_CONNECTED ) {
        return;
    }

    // make them a spectator
    SetTeam( &g_entities[ clientNum ], "s" );
}

/*
=======================
RemoveTournamentWinner
=======================
*/
void RemoveTournamentWinner( void ) {
    int			clientNum;

    if ( level.numPlayingClients != 2 ) {
        return;
    }

    clientNum = level.sortedClients[0];

    if ( level.clients[ clientNum ].pers.connected != CON_CONNECTED ) {
        return;
    }

    // make them a spectator
    SetTeam( &g_entities[ clientNum ], "s" );
}

/*
=======================
AdjustTournamentScores
=======================
*/
void AdjustTournamentScores( void ) {
    int			clientNum;

    clientNum = level.sortedClients[0];
    if ( level.clients[ clientNum ].pers.connected == CON_CONNECTED ) {
        level.clients[ clientNum ].sess.wins++;
        ClientUserinfoChanged( clientNum );
    }

    clientNum = level.sortedClients[1];
    if ( level.clients[ clientNum ].pers.connected == CON_CONNECTED ) {
        level.clients[ clientNum ].sess.losses++;
        ClientUserinfoChanged( clientNum );
    }

}

/*
=============
SortRanks

=============
*/
int QDECL SortRanks( const void *a, const void *b ) {
    gclient_t	*ca, *cb;

    ca = &level.clients[*(int *)a];
    cb = &level.clients[*(int *)b];

    // sort special clients last
    if ( ca->sess.spectatorState == SPECTATOR_SCOREBOARD || ca->sess.spectatorClient < 0 ) {
        return 1;
    }
    if ( cb->sess.spectatorState == SPECTATOR_SCOREBOARD || cb->sess.spectatorClient < 0  ) {
        return -1;
    }

    // then connecting clients
    if ( ca->pers.connected == CON_CONNECTING ) {
        return 1;
    }
    if ( cb->pers.connected == CON_CONNECTING ) {
        return -1;
    }


    // then spectators
    if ( ca->sess.sessionTeam == TEAM_SPECTATOR && cb->sess.sessionTeam == TEAM_SPECTATOR ) {
        if ( ca->sess.spectatorTime < cb->sess.spectatorTime ) {
            return -1;
        }
        if ( ca->sess.spectatorTime > cb->sess.spectatorTime ) {
            return 1;
        }
        return 0;
    }
    if ( ca->sess.sessionTeam == TEAM_SPECTATOR ) {
        return 1;
    }
    if ( cb->sess.sessionTeam == TEAM_SPECTATOR ) {
        return -1;
    }

    // then sort by score
    if ( ca->pers.nsPC.entire_xp
            > cb->pers.nsPC.entire_xp ) {
        return -1;
    }
    if ( ca->pers.nsPC.entire_xp
            < cb->pers.nsPC.entire_xp ) {
        return 1;
    }
    return 0;
}

/*
============
CalculateRanks

Recalculates the score ranks of all players
This will be called on every client connect, begin, disconnect, death,
and team change.
============
*/
void CalculateRanks( void ) {
    int		i;
    int		rank;
    int		score;
    int		newScore;
    gclient_t	*cl;

    level.follow1 = -1;
    level.follow2 = -1;
    level.numConnectedClients = 0;
    level.numNonSpectatorClients = 0;
    level.numPlayingClients = 0;
    level.numVotingClients = 0;		// don't count bots
    for ( i = 0; i < TEAM_NUM_TEAMS; i++ ) {
        level.numteamVotingClients[i] = 0;
    }
    for ( i = 0 ; i < level.maxclients ; i++ ) {
        if ( level.clients[i].pers.connected != CON_DISCONNECTED ) {
            level.sortedClients[level.numConnectedClients] = i;
            level.numConnectedClients++;

            if ( level.clients[i].sess.sessionTeam != TEAM_SPECTATOR ) {
                level.numNonSpectatorClients++;

                // decide if this should be auto-followed
                if ( level.clients[i].pers.connected == CON_CONNECTED ) {
                    level.numPlayingClients++;
                    if ( !(g_entities[i].r.svFlags & SVF_BOT) ) {
                        level.numVotingClients++;
                        if ( level.clients[i].sess.sessionTeam == TEAM_RED )
                            level.numteamVotingClients[0]++;
                        else if ( level.clients[i].sess.sessionTeam == TEAM_BLUE )
                            level.numteamVotingClients[1]++;
                    }
                    if ( level.follow1 == -1 ) {
                        level.follow1 = i;
                    } else if ( level.follow2 == -1 ) {
                        level.follow2 = i;
                    }
                }
            }
        }
    }

    qsort( level.sortedClients, level.numConnectedClients,
           sizeof(level.sortedClients[0]), SortRanks );

    // set the rank value for all clients that are connected and not spectators
    if ( g_gametype.integer >= GT_TEAM ) {
        // in team games, rank is just the order of the teams, 0=red, 1=blue, 2=tied
        for ( i = 0;  i < level.numConnectedClients; i++ ) {
            cl = &level.clients[ level.sortedClients[i] ];
            if ( level.teamScores[TEAM_RED] == level.teamScores[TEAM_BLUE] ) {
                cl->ps.persistant[PERS_RANK] = 2;
            } else if ( level.teamScores[TEAM_RED] > level.teamScores[TEAM_BLUE] ) {
                cl->ps.persistant[PERS_RANK] = 0;
            } else {
                cl->ps.persistant[PERS_RANK] = 1;
            }
        }
    } else {
        rank = -1;
        score = 0;
        for ( i = 0;  i < level.numPlayingClients; i++ ) {
            cl = &level.clients[ level.sortedClients[i] ];
            newScore = cl->pers.nsPC.entire_xp;
            if ( i == 0 || newScore != score ) {
                rank = i;
                // assume we aren't tied until the next client is checked
                level.clients[ level.sortedClients[i] ].ps.persistant[PERS_RANK] = rank;
            } else {
                // we are tied with the previous client
                level.clients[ level.sortedClients[i-1] ].ps.persistant[PERS_RANK] = rank | RANK_TIED_FLAG;
                level.clients[ level.sortedClients[i] ].ps.persistant[PERS_RANK] = rank | RANK_TIED_FLAG;
            }
            score = newScore;
            if ( g_gametype.integer == GT_TEAM && level.numPlayingClients == 1 ) {
                level.clients[ level.sortedClients[i] ].ps.persistant[PERS_RANK] = rank | RANK_TIED_FLAG;
            }
        }
    }

    // set the CS_SCORES1/2 configstrings, which will be visible to everyone
    if ( g_gametype.integer >= GT_TEAM ) {
        trap_SetConfigstring( CS_SCORES1, va("%i", level.teamScores[TEAM_RED] ) );
        trap_SetConfigstring( CS_SCORES2, va("%i", level.teamScores[TEAM_BLUE] ) );
    } else {
        if ( level.numConnectedClients == 0 ) {
            trap_SetConfigstring( CS_SCORES1, va("%i", SCORE_NOT_PRESENT) );
            trap_SetConfigstring( CS_SCORES2, va("%i", SCORE_NOT_PRESENT) );
        } else if ( level.numConnectedClients == 1 ) {
            trap_SetConfigstring( CS_SCORES1, va("%i", level.clients[ level.sortedClients[0] ].ps.persistant[PERS_SCORE] ) );
            trap_SetConfigstring( CS_SCORES2, va("%i", SCORE_NOT_PRESENT) );
        } else {
            trap_SetConfigstring( CS_SCORES1, va("%i", level.clients[ level.sortedClients[0] ].ps.persistant[PERS_SCORE] ) );
            trap_SetConfigstring( CS_SCORES2, va("%i", level.clients[ level.sortedClients[1] ].ps.persistant[PERS_SCORE] ) );
        }
    }

    // see if it is time to end the level
    CheckExitRules();

    // if we are at the intermission, send the new info to everyone
    if ( level.intermissiontime ) {
        SendScoreboardMessageToAllClients();
    }
}


/*
========================================================================

MAP CHANGING

========================================================================
*/

/*
========================
SendScoreboardMessageToAllClients

Do this at BeginIntermission time and whenever ranks are recalculated
due to enters/exits/forced team changes
========================
*/
void SendScoreboardMessageToAllClients( void ) {
    int		i;

    for ( i = 0 ; i < level.maxclients ; i++ ) {
        if ( level.clients[ i ].pers.connected == CON_CONNECTED ) {
            DeathmatchScoreboardMessage( g_entities + i );
        }
    }
}

/*
========================
MoveClientToIntermission

When the intermission starts, this will be called for all players.
If a new client connects, this will be called after the spawn function.
========================
*/
void MoveClientToIntermission( gentity_t *ent ) {
    // take out of follow mode if needed
    if ( ent->client->sess.spectatorState == SPECTATOR_FOLLOW ) {
        StopFollowing( ent );
    }


    // move to the spot
    VectorCopy( level.intermission_origin, ent->s.origin );
    VectorCopy( level.intermission_origin, ent->client->ps.origin );
    VectorCopy (level.intermission_angle, ent->client->ps.viewangles);
    ent->client->ps.pm_type = PM_INTERMISSION;

    // clean up powerup info
    memset( ent->client->ps.powerups, 0, sizeof(ent->client->ps.powerups) );

    ent->client->ps.eFlags = 0;
    ent->s.eFlags = 0;
    ent->s.eType = ET_GENERAL;
    ent->s.modelindex = 0;
    ent->s.loopSound = 0;
    ent->s.event = 0;
    ent->r.contents = 0;
}

/*
==================
FindIntermissionPoint

This is also used for spectator spawns
==================
*/
void FindIntermissionPoint( void ) {
    gentity_t	*ent, *target;
    vec3_t		dir;

    // find the intermission spot
    ent = G_Find (NULL, FOFS(classname), "info_player_intermission");
    if ( !ent ) {	// the map creator forgot to put in an intermission point...
        SelectSpawnPoint ( vec3_origin, level.intermission_origin, level.intermission_angle );
    } else {
        VectorCopy (ent->s.origin, level.intermission_origin);
        VectorCopy (ent->s.angles, level.intermission_angle);
        // if it has a target, look towards it
        if ( ent->target ) {
            target = G_PickTarget( ent->target );
            if ( target ) {
                VectorSubtract( target->s.origin, level.intermission_origin, dir );
                vectoangles( dir, level.intermission_angle );
            }
        }
    }

}

/*
==================
BeginIntermission
==================
*/
void BeginIntermission( void ) {
    int			i;
    gentity_t	*client;

    if ( level.intermissiontime ) {
        return;		// already active
    }

    // Navy Seals ++
    LTS_Rounds = 1;
    // Navy Seals --
    level.intermissiontime = level.time;
    FindIntermissionPoint();


    // if single player game

    // move all clients to the intermission point
    for (i=0 ; i< level.maxclients ; i++) {
        client = g_entities + i;
        if (!client->inuse)
            continue;
        // respawn if dead
        if (client->health <= 0) {
            respawn(client);
        }
        MoveClientToIntermission( client );
    }

    // send the current scoring to all clients
    SendScoreboardMessageToAllClients();

}


/*
=============
ExitLevel

When the intermission has been exited, the server is either killed
or moved to a new level based on the "nextmap" cvar

=============
*/
void ExitLevel (void) {
    int		i;
    gclient_t *cl;

    if ( level.mapCycleNumMaps > 0 )
        trap_SendConsoleCommand( EXEC_APPEND, va( "map %s\n", NS_GetNextMap() )  );
    else
        trap_SendConsoleCommand( EXEC_APPEND, "vstr nextmap\n" );

    level.changemap = NULL;
    level.intermissiontime = 0;

    // reset all the scores so we don't enter the intermission again
    level.teamScores[TEAM_RED] = 0;
    level.teamScores[TEAM_BLUE] = 0;
    for ( i=0 ; i< g_maxclients.integer ; i++ ) {
        cl = level.clients + i;
        if ( cl->pers.connected != CON_CONNECTED ) {
            continue;
        }
        cl->ps.persistant[PERS_SCORE] = 0;
    }

    // we need to do this here before chaning to CON_CONNECTING
    G_WriteSessionData();

    // change all client states to connecting, so the early players into the
    // next level will know the others aren't done reconnecting
    for (i=0 ; i< g_maxclients.integer ; i++) {
        if ( level.clients[i].pers.connected == CON_CONNECTED ) {
            level.clients[i].pers.connected = CON_CONNECTING;
        }
    }

}

/*
=================
G_LogPrintf

Print to the logfile with a time stamp if it is open
=================
*/
#define LOGLINEBUFFER	1024
void QDECL G_LogPrintf( const char *fmt, ... ) {
    va_list		argptr;
    char		string[LOGLINEBUFFER];
    int			min, tens, sec;
	int			length;

    sec = level.time / 1000;

    min = sec / 60;
    sec -= min * 60;
    tens = sec / 10;
    sec -= tens * 10;

    Com_sprintf( string, sizeof(string), "%3i:%i%i ", min, tens, sec );

    va_start( argptr, fmt );
    vsprintf( string +7 , fmt,argptr );
    va_end( argptr );

    if ( g_dedicated.integer ) {
        G_Printf( "%s", string + 7 );
    }

    if ( !level.logFile ) {
        return;
    }

	length = strlen( string );
	if ( string[length-1] == '\n' && length < LOGLINEBUFFER - 2)
	{
		string[length-1] = '\r';
		string[length] = '\n';
		string[length+1] = '\0';
		length = strlen( string );
	}
    trap_FS_Write( string, length, level.logFile );
}

/*
================
LogExit

Append information about this game to the log file
================
*/
void LogExit( const char *string ) {
    int				i, numSorted;
    gclient_t		*cl;
    //	qboolean won = qtrue;

    G_LogPrintf( "Exit: %s\n", string );

    level.intermissionQueued = level.time;

    // this will keep the clients from playing any voice sounds
    // that will get cut off when the queued intermission starts
    trap_SetConfigstring( CS_INTERMISSION, "1" );

    // don't send more than 32 scores (FIXME?)
    numSorted = level.numConnectedClients;
    if ( numSorted > 32 ) {
        numSorted = 32;
    }

    if ( g_gametype.integer >= GT_TEAM ) {
        G_LogPrintf( "red:%i  blue:%i\n",
                     level.teamScores[TEAM_RED], level.teamScores[TEAM_BLUE] );
    }

    for (i=0 ; i < numSorted ; i++) {
        int		ping;

        cl = &level.clients[level.sortedClients[i]];

        if ( cl->sess.sessionTeam == TEAM_SPECTATOR ) {
            continue;
        }
        if ( cl->pers.connected == CON_CONNECTING ) {
            continue;
        }

        ping = cl->ps.ping < 999 ? cl->ps.ping : 999;

        G_LogPrintf( "xp: %i  ping: %i  client: %i %s\n", cl->pers.nsPC.entire_xp, ping, level.sortedClients[i],	cl->pers.netname );


    }




}


/*
=================
CheckIntermissionExit

The level will stay at the intermission for a minimum of 5 seconds
If all players wish to continue, the level will then exit.
If one or more players have not acknowledged the continue, the game will
wait 10 seconds before going on.
=================
*/
void CheckIntermissionExit( void ) {
    int			ready, notReady;
    int			i;
    gclient_t	*cl;
    int			readyMask;
    qboolean	emptyServer = qtrue;

    // see which players are ready
    ready = 0;
    notReady = 0;
    readyMask = 0;
    for (i=0 ; i< g_maxclients.integer ; i++) {
        cl = level.clients + i;
        if ( cl->pers.connected != CON_CONNECTED ) {
            continue;
        }
        if ( g_entities[cl->ps.clientNum].r.svFlags & SVF_BOT ) {
            continue;
        }

        if ( emptyServer )
            emptyServer = qfalse;

        if ( cl->readyToExit ) {
            ready++;
            if ( i < 16 ) {
                readyMask |= 1 << i;
            }
        } else {
            notReady++;
        }
    }

    // copy the readyMask to each player's stats so
    // it can be displayed on the scoreboard
    for (i=0 ; i< g_maxclients.integer ; i++) {
        cl = level.clients + i;
        if ( cl->pers.connected != CON_CONNECTED ) {
            continue;
        }
        cl->ps.stats[STAT_CLIENTS_READY] = readyMask;
    }

    // never exit in less than five seconds
    if ( level.time < level.intermissiontime + 5000 ) {
        return;
    }

    if ( emptyServer ) {
        ExitLevel();
        return;
    }
    // if nobody wants to go, clear timer
    if ( !ready ) {
        level.readyToExit = qfalse;
        return;
    }

    // if everyone wants to go, go now
    if ( !notReady ) {
        ExitLevel();
        return;
    }

    // the first person to ready starts the ten second timeout
    if ( !level.readyToExit ) {
        level.readyToExit = qtrue;
        level.exitTime = level.time;
    }

    // if we have waited ten seconds since at least one player
    // wanted to exit, go ahead
    if ( level.time < level.exitTime + 10000 ) {
        return;
    }

    ExitLevel();
}

/*
=============
ScoreIsTied
=============
*/
qboolean ScoreIsTied( void ) {
    int		a, b;

    if ( level.numPlayingClients < 2 ) {
        return qfalse;
    }

    if ( g_gametype.integer >= GT_TEAM ) {
        return level.teamScores[TEAM_RED] == level.teamScores[TEAM_BLUE];
    }

    a = level.clients[level.sortedClients[0]].ps.persistant[PERS_SCORE];
    b = level.clients[level.sortedClients[1]].ps.persistant[PERS_SCORE];

    return a == b;
}

/*
=================
CheckExitRules

There will be a delay between the time the exit is qualified for
and the time everyone is moved to the intermission spot, so you
can see the last frag.
=================
*/
int NS_GetVip(team_t team );
void CheckExitRules( void ) {
    int			i;
    gclient_t	*cl;
    // if at the intermission, wait for all non-bots to
    // signal ready, then go to next level
    if ( level.intermissiontime ) {
        CheckIntermissionExit ();
        return;
    }

    if ( level.intermissionQueued ) {
#ifdef MISSIONPACK
        int time = (g_singlePlayer.integer) ? SP_INTERMISSION_DELAY_TIME : INTERMISSION_DELAY_TIME;
        if ( level.time - level.intermissionQueued >= time ) {
            level.intermissionQueued = 0;
            BeginIntermission();
        }
#else
        if ( level.time - level.intermissionQueued >= INTERMISSION_DELAY_TIME ) {
            level.intermissionQueued = 0;
            BeginIntermission();
        }
#endif
        return;
    }

    if ( g_timelimit.integer ) {
        if ( level.time - level.startTime >= g_timelimit.integer*60000 ) {
            trap_SendServerCommand( -1, "print \"Timelimit hit.\n\"");
            LogExit( "Timelimit hit." );
            return;
        }
    }

    // if a running round
    if ( g_roundTime.integer && level.warmupTime == -1 && GameState == STATE_LOCKED) {
        if ( NS_CheckEndRound( ) )
        {
            if ( g_gametype.integer != GT_LTS )
                return;

            // see if the round has to be ended this round.
            // it should be ended when all objectives have been accomplished.
            if ( !g_overrideGoals.integer )
            {
                if ( ( level.done_objectives[TEAM_RED] >= level.num_objectives[TEAM_RED] ) && level.num_objectives[TEAM_RED] > 0 )
                {
                    NS_EndRoundForTeam( TEAM_RED );
                    return;
                }
                else if ( ( level.done_objectives[TEAM_BLUE] >= level.num_objectives[TEAM_BLUE] ) && level.num_objectives[TEAM_BLUE] > 0 )
                { // don't go any further
                    NS_EndRoundForTeam( TEAM_BLUE );
                    return;
                }
            }

            // Navy Seals ++

            if ( level.vip[TEAM_RED] == VIP_STAYALIVE && NS_IsVipAlive ( -1, TEAM_RED ) && !g_overrideGoals.integer  ) {
                level.done_objectives[TEAM_RED]++;
                return;
            }
            else if ( level.vip[TEAM_BLUE] == VIP_STAYALIVE && NS_IsVipAlive ( -1, TEAM_BLUE ) && !g_overrideGoals.integer ) {
                level.done_objectives[TEAM_BLUE]++;
                return;
            }
            else if ( level.drawWinner > TEAM_FREE && !g_overrideGoals.integer && AliveTeamCount(-1, TEAM_RED) > 0 && AliveTeamCount(-1, TEAM_BLUE) > 0 )
            {
                PrintMsg( NULL, "Roundtimelimit hit. Winner %s\n", TeamName( level.drawWinner ) );
                NS_EndRoundForTeam( level.drawWinner );
            }
            else
                // Navy Seals --
            {
                PrintMsg( NULL ,"Roundtimelimit hit.\n");
                NS_EndRoundForTeam( TEAM_FREE );
            }
            //	NS_EndRound(); // end the round then
            //	return;
            return;
        }
    }

    // check for sudden death
    if ( ScoreIsTied() ) {
        // always wait for sudden death
        return;
    }



    if ( level.numPlayingClients < 2 ) {
        return;
    }

    if ( g_gametype.integer < GT_TEAM && g_fraglimit.integer ) {
        if ( level.teamScores[TEAM_RED] >= g_fraglimit.integer ) {
            trap_SendServerCommand( -1, "print \"The Seals hit the fraglimit.\n\"" );
            LogExit( "Fraglimit hit." );
            return;
        }

        if ( level.teamScores[TEAM_BLUE] >= g_fraglimit.integer ) {
            trap_SendServerCommand( -1, "print \"The Tangos hit the fraglimit.\n\"" );
            LogExit( "Fraglimit hit." );
            return;
        }

        for ( i=0 ; i< g_maxclients.integer ; i++ ) {
            cl = level.clients + i;
            if ( cl->pers.connected != CON_CONNECTED ) {
                continue;
            }
            if ( cl->sess.sessionTeam != TEAM_FREE ) {
                continue;
            }

            if ( cl->ps.persistant[PERS_SCORE] >= g_fraglimit.integer ) {
                LogExit( "Fraglimit hit." );
                trap_SendServerCommand( -1, va("print \"%s" S_COLOR_WHITE " hit the fraglimit.\n\"",
                                               cl->pers.netname ) );
                return;
            }
        }
    }

    if ( g_gametype.integer >= GT_TEAM && g_capturelimit.integer ) {

        if ( level.teamScores[TEAM_RED] >= g_capturelimit.integer ) {
            trap_SendServerCommand( -1, "print \"The Seals hit the pointlimit.\n\"" );
            LogExit( "Pointlimit hit." );
            return;
        }

        if ( level.teamScores[TEAM_BLUE] >= g_capturelimit.integer ) {
            trap_SendServerCommand( -1, "print \"The Tangos hit the pointlimit.\n\"" );
            LogExit( "Pointlimit hit." );
            return;
        }
    }
}



/*
========================================================================

FUNCTIONS CALLED EVERY FRAME

========================================================================
*/

/*
==================
CheckVote
==================
*/
void CheckVote( void ) {
    if ( level.voteExecuteTime && level.voteExecuteTime < level.time ) {
        level.voteExecuteTime = 0;
        trap_SendConsoleCommand( EXEC_APPEND, va("%s\n", level.voteString ) );
    }
    if ( !level.voteTime ) {
        return;
    }
    if ( level.time - level.voteTime >= VOTE_TIME ) {
        trap_SendServerCommand( -1, "print \"Vote failed.\n\"" );
    } else {
        if ( level.voteYes > level.numVotingClients/2 ) {
            // execute the command, then remove the vote
            trap_SendServerCommand( -1, "print \"Vote passed.\n\"" );
            level.voteExecuteTime = level.time + 3000;
        } else if ( level.voteNo >= level.numVotingClients/2 ) {
            // same behavior as a timeout
            trap_SendServerCommand( -1, "print \"Vote failed.\n\"" );
        } else {
            // still waiting for a majority
            return;
        }
    }
    level.voteTime = 0;
    trap_SetConfigstring( CS_VOTE_TIME, "" );

}

/*
==================
PrintTeam
==================
*/
void PrintTeam(int team, char *message) {
    int i;

    for ( i = 0 ; i < level.maxclients ; i++ ) {
        if (level.clients[i].sess.sessionTeam != team)
            continue;
        trap_SendServerCommand( i, message );
    }
}

/*
==================
SetLeader
==================
*/
void SetLeader(int team, int client) {
    int i;

    if ( level.clients[client].pers.connected == CON_DISCONNECTED ) {
        PrintTeam(team, va("print \"%s is not connected\n\"", level.clients[client].pers.netname) );
        return;
    }
    if (level.clients[client].sess.sessionTeam != team) {
        PrintTeam(team, va("print \"%s is not on the team anymore\n\"", level.clients[client].pers.netname) );
        return;
    }
    for ( i = 0 ; i < level.maxclients ; i++ ) {
        if (level.clients[i].sess.sessionTeam != team)
            continue;
        if (level.clients[i].sess.teamLeader) {
            level.clients[i].sess.teamLeader = qfalse;
            ClientUserinfoChanged(i);
        }
    }
    level.clients[client].sess.teamLeader = qtrue;
    ClientUserinfoChanged( client );
    PrintTeam(team, va("print \"%s is the new team leader\n\"", level.clients[client].pers.netname) );
}

/*
==================
CheckTeamLeader
==================
*/
void CheckTeamLeader( int team ) {
    int i;

    for ( i = 0 ; i < level.maxclients ; i++ ) {
        if (level.clients[i].sess.sessionTeam != team)
            continue;
        if (level.clients[i].sess.teamLeader)
            break;
    }
    if (i >= level.maxclients) {
        for ( i = 0 ; i < level.maxclients ; i++ ) {
            if (level.clients[i].sess.sessionTeam != team)
                continue;
            if (!(g_entities[i].r.svFlags & SVF_BOT)) {
                level.clients[i].sess.teamLeader = qtrue;
                break;
            }
        }
        for ( i = 0 ; i < level.maxclients ; i++ ) {
            if (level.clients[i].sess.sessionTeam != team)
                continue;
            level.clients[i].sess.teamLeader = qtrue;
            break;
        }
    }
}

/*
==================
CheckTeamVote
==================
*/
void CheckTeamVote( int team ) {
    int cs_offset;

    if ( team == TEAM_RED )
        cs_offset = 0;
    else if ( team == TEAM_BLUE )
        cs_offset = 1;
    else
        return;

    if ( !level.teamVoteTime[cs_offset] ) {
        return;
    }
    if ( level.time - level.teamVoteTime[cs_offset] >= VOTE_TIME ) {
        trap_SendServerCommand( -1, "print \"Team vote failed.\n\"" );
    } else {
        if ( level.teamVoteYes[cs_offset] > level.numteamVotingClients[cs_offset]/2 ) {
            // execute the command, then remove the vote
            trap_SendServerCommand( -1, "print \"Team vote passed.\n\"" );
            //
            if ( !Q_strncmp( "leader", level.teamVoteString[cs_offset], 6) ) {
                //set the team leader
                SetLeader(team, atoi(level.teamVoteString[cs_offset] + 7));
            }
            else {
                trap_SendConsoleCommand( EXEC_APPEND, va("%s\n", level.teamVoteString[cs_offset] ) );
            }
        } else if ( level.teamVoteNo[cs_offset] >= level.numteamVotingClients[cs_offset]/2 ) {
            // same behavior as a timeout
            trap_SendServerCommand( -1, "print \"Team vote failed.\n\"" );
        } else {
            // still waiting for a majority
            return;
        }
    }
    level.teamVoteTime[cs_offset] = 0;
    trap_SetConfigstring( CS_TEAMVOTE_TIME + cs_offset, "" );

}


/*
==================
CheckCvars
==================
*/
void CheckCvars( void ) {
    static int lastMod = -1;

    if ( g_password.modificationCount != lastMod ) {
        lastMod = g_password.modificationCount;
        if ( *g_password.string && Q_stricmp( g_password.string, "none" ) ) {
            trap_Cvar_Set( "g_needpass", "1" );
        } else {
            trap_Cvar_Set( "g_needpass", "0" );
        }
    }
}

/*
=============
G_RunThink

Runs thinking code for this frame if necessary
=============
*/
void G_RunThink (gentity_t *ent) {
    float	thinktime;

    thinktime = ent->nextthink;
    if (thinktime <= 0) {
        return;
    }
    if (thinktime > level.time) {
        return;
    }

    ent->nextthink = 0;
    if (!ent->think) {
        G_Error ( "NULL ent->think [ %s ]", ent->classname );
    }
    ent->think (ent);
}

/*
================
G_RunFrame

Advances the non-player objects in the world
================
*/
void G_RunFrame( int levelTime ) {
    int			i;
    gentity_t	*ent;
    int			msec;
    int start, end;

    // if we are waiting for the level to restart, do nothing
    if ( level.restarted ) {
        return;
    }

    level.framenum++;
    level.previousTime = level.time;
    level.time = levelTime;
    msec = level.time - level.previousTime;

    // get any cvar changes
    G_UpdateCvars();

    // update IRC bot cvars
    G_SetupServerInfo();


    //
    // go through all allocated objects
    //
    start = level.frameStartTime = trap_Milliseconds();
    ent = &g_entities[0];
    for (i=0 ; i<level.num_entities ; i++, ent++) {
        if ( !ent->inuse ) {
            continue;
        }

        // clear events that are too old
        if ( level.time - ent->eventTime > EVENT_VALID_MSEC ) {
            if ( ent->s.event ) {
                ent->s.event = 0;	// &= EV_EVENT_BITS;
                if ( ent->client ) {
                    ent->client->ps.externalEvent = 0;
                    // predicted events should never be set to zero
                    //ent->client->ps.events[0] = 0;
                    //ent->client->ps.events[1] = 0;
                }
            }
            if ( ent->freeAfterEvent ) {
                // tempEntities or dropped items completely go away after their event
                G_FreeEntity( ent );
                continue;
            } else if ( ent->unlinkAfterEvent ) {
                // items that will respawn will hide themselves after their pickup event
                ent->unlinkAfterEvent = qfalse;
                trap_UnlinkEntity( ent );
            }
        }

        // temporary entities don't think
        if ( ent->freeAfterEvent ) {
            continue;
        }

        if ( !ent->r.linked && ent->neverFree ) {
            continue;
        }

        if ( ent->s.eType == ET_MISSILE || ent->s.eType == ET_BULLET ) {
            G_RunMissile( ent );
            continue;
        }

        if ( ent->s.eType == ET_ITEM || ent->physicsObject ) {
            G_RunItem( ent );
            continue;
        }

        /*		if ( ent->s.eType == ET_ACTOR )
        {
        G_RunActor( ent );
        continue;
        }*/

        if ( ent->s.eType == ET_MOVER ||
                ent->s.eType == ET_DOOR ||
                ent->s.eType == ET_FUNCEXPLOSIVE ) {
            G_RunMover( ent );
            continue;
        }

        if ( i < MAX_CLIENTS ) {
            G_RunClient( ent );
            continue;
        }

        G_RunThink( ent );
    }
    end = trap_Milliseconds();

    start = trap_Milliseconds();
    // perform final fixups on the players
    ent = &g_entities[0];
    for (i=0 ; i < level.maxclients ; i++, ent++ ) {
        if ( ent->inuse ) {
            ClientEndFrame( ent );
        }
    }
    end = trap_Milliseconds();

    // see if it is time to do a tournement restart
    //	CheckTournament();

    // see if it is time to end the level
    CheckExitRules();

    // update to team status?
    //	CheckTeamStatus();

    // Navy Seals ++
    CheckTeamplay();
    // Navy Seals --
    // cancel vote if timed out
    CheckVote();

    // check team votes
    CheckTeamVote( TEAM_RED );
    CheckTeamVote( TEAM_BLUE );

    // for tracking changes
    CheckCvars();


    if (g_listEntity.integer) {
        for (i = 0; i < MAX_GENTITIES; i++) {
            G_Printf("%4i: %s\n", i, g_entities[i].classname);
        }
        trap_Cvar_Set("g_listEntity", "0");
    }
}
